package com.denghq.projectbuilder.component.config.domain;

import com.denghq.projectbuilder.component.config.metadata.ConfigDescriptor;
import com.denghq.projectbuilder.component.config.metadata.ConfigOptionDescriptor;
import com.denghq.projectbuilder.component.config.metadata.CustomDataSource;
import com.denghq.projectbuilder.component.config.metadata.annotation.Category;
import com.denghq.projectbuilder.component.config.metadata.annotation.ConfigAttribute;
import com.denghq.projectbuilder.component.config.metadata.annotation.OptionDataSource;
import com.denghq.projectbuilder.component.config.metadata.enums.ConfigValueTypeEnum;
import com.denghq.projectbuilder.component.config.metadata.enums.DataSourceEnum;
import com.google.common.collect.Lists;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;


@Slf4j
public class ConfigClassParser {

    private final Set<Class> configClasses;

    @Getter
    private final String appNo;

    @Getter
    private List<ConfigDescriptor> configDescriptorListList;

    public ConfigClassParser(Set<Class> configClasses, String appNo) {
        this.configClasses = configClasses;
        this.appNo = appNo;
        parse();
    }

    public void parse() {
        configDescriptorListList = Lists.newArrayList();
        if (!CollectionUtils.isEmpty(configClasses)) {
            configClasses.forEach(c -> {
                        try {
                            Object inst = c.getDeclaredConstructor().newInstance();
                            configDescriptorListList.add(getConfigDescriptor(inst));
                        } catch (Exception e) {
                            log.warn("实例化配置类【{}】出错，{}", c.getName(), e.getMessage());
                        }
                    }

            );
        }
    }

    /**
     * 设置配置类描述信息
     *
     * @param configInst 配置类实例
     */
    public ConfigDescriptor getConfigDescriptor(Object configInst) {
        ConfigDescriptor configDescriptor = getConfigCategoryInst(configInst);
        fillConfigOptionDescriptors(configDescriptor, configInst);
        return configDescriptor;
    }

    private ConfigDescriptor getConfigCategoryInst(Object configInst) {
        Class clazz = configInst.getClass();
        ConfigDescriptor configDescriptor = new ConfigDescriptor();
        // 获取配置分类注解
        Category configCategory
                = (Category) clazz.getAnnotation(Category.class);
        Assert.notNull(configCategory, "未从【" + clazz.getSimpleName() + "】类解析到【" + configCategory.getClass() + "】注解");
        configDescriptor.setConfigClass(clazz);
        //设置是否自动注册

        //系统当前应用的应用标识
        Assert.notNull(appNo, "应用标识不能为空，请设置spring.application.name");
        if (StringUtils.isBlank(configCategory.appNo())) { //如果为空，设置当前应用的应用标识
            configDescriptor.setAppNo(appNo);
        } else {
            configDescriptor.setAppNo(configCategory.appNo());
        }

        if (configCategory.autoRegiste()) {
            if (!configDescriptor.getAppNo().equals(appNo)) {
                configDescriptor.setAutoRegiste(false);
                log.warn("配置类【{}】,非本应用配置，不会自动注册，appNo:{}", configCategory.categoryName(), configCategory.appNo());
            } else {
                configDescriptor.setAutoRegiste(true);
            }
        } else {
            configDescriptor.setAutoRegiste(false);
        }

        configDescriptor.setCategoryName(configCategory.categoryName());
        configDescriptor.setProjectName(configCategory.projectName());
        configDescriptor.setConfigType(configCategory.configType().getValue());
        configDescriptor.setProjectName(configCategory.projectName());
        configDescriptor.setCategoryNo(configCategory.categoryNo());
        configDescriptor.setCategoryName(configCategory.categoryName());
        configDescriptor.setSaveVerifyApi(configCategory.saveVerifyApi());

        //设置prefix
        ConfigurationProperties configurationProperties = (ConfigurationProperties) clazz.getAnnotation(ConfigurationProperties.class);
        if (configurationProperties != null) {
            configDescriptor.setPrefix(StringUtils.isBlank(configurationProperties.value()) ? configurationProperties.prefix() : configurationProperties.value());
        }
        return configDescriptor;
    }

    /**
     * 设置配置类描述配置项信息
     *
     * @param configDescriptor
     * @param configInst
     */
    private void fillConfigOptionDescriptors(ConfigDescriptor configDescriptor, Object configInst) {

        Class clazz = configInst.getClass();
        // 获得字段注解
        Field[] fields = clazz.getDeclaredFields();

        if (fields != null && fields.length > 0) {
            List<ConfigOptionDescriptor> options = Lists.newArrayList();
            Arrays.stream(fields).forEach(field -> {
                field.setAccessible(true);
                Optional.ofNullable(getConfigOptionDescriptor(field, configInst)).ifPresent(options::add);
            });
            configDescriptor.setOptionSettings(options);
        }


    }

    /**
     * 获取字段对应的配置类描述
     *
     * @param field      java字段
     * @param configInst 配置类实例
     * @return
     */
    private ConfigOptionDescriptor getConfigOptionDescriptor(Field field, Object configInst) {
        ConfigAttribute configAttr = field.getAnnotation(ConfigAttribute.class);
        if (configAttr != null) {
            ConfigOptionDescriptor configOptionDescriptor = new ConfigOptionDescriptor();
            configOptionDescriptor.setPrimaryKey(configAttr.primaryKey());
            configOptionDescriptor.setField(field);
            //设置属性key默认为属性值
            configOptionDescriptor.setConfigKey(StringUtils.isBlank(configAttr.key()) ? field.getName() : configAttr.key());
            configOptionDescriptor.setConfigName(configAttr.name());
            //设置属性值类型
            ConfigValueTypeEnum valueType = configAttr.valueType();
            //如果没有指定则自动判断
            if (configAttr.valueType() == ConfigValueTypeEnum.NULL) {
                valueType = getValueType(field);
            }
            configOptionDescriptor.setConfigValueType(valueType.getValue());

            //设置复杂类型元素/对象的字段描述信息 optionSettings
            //object
            if (valueType == ConfigValueTypeEnum.OBJECT) {
                Field[] declaredFields = field.getType().getDeclaredFields();
                if (declaredFields != null || declaredFields.length >= 0) {
                    List<ConfigOptionDescriptor> options = Lists.newArrayList();
                    Arrays.stream(declaredFields).forEach(f -> {
                        Optional.ofNullable(getConfigOptionDescriptor(f, null)).ifPresent(options::add);
                    });
                    configOptionDescriptor.setOptionSettings(options);
                }
            }

            //data list
            if (valueType == ConfigValueTypeEnum.DATA_LIST) {
                ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
                Type[] argumentsType = parameterizedType.getActualTypeArguments();
                Type actualTypeArgument = argumentsType[0];
                try {
                    Class<?> aClass = this.getClass().getClassLoader().loadClass(actualTypeArgument.getTypeName());

                    ConfigValueTypeEnum simpleValueType = getSimpleValueType(aClass.getTypeName());

                    if (simpleValueType == ConfigValueTypeEnum.OBJECT) {
                        //Class<? extends Type> argumentClass = actualTypeArgument.getClass();
                        Field[] declaredFields = aClass.getDeclaredFields();
                        if (declaredFields != null || declaredFields.length > 0) {
                            List<ConfigOptionDescriptor> options = Lists.newArrayList();
                            Arrays.stream(declaredFields).forEach(f -> {
                                Optional.ofNullable(getConfigOptionDescriptor(f, null)).ifPresent(options::add);
                            });
                            configOptionDescriptor.setOptionSettings(options);
                        }
                    } else {
                        List<ConfigOptionDescriptor> options = Lists.newArrayList();
                        ConfigOptionDescriptor optionDescriptor = new ConfigOptionDescriptor();
                        optionDescriptor.setConfigValueType(simpleValueType.getValue());
                        //数组元素是基础类型没有primary key注解，直接设为false
                        optionDescriptor.setPrimaryKey(false);
                        options.add(optionDescriptor);
                        configOptionDescriptor.setOptionSettings(options);
                    }

                } catch (ClassNotFoundException e) {
                    log.warn(e.getMessage());
                }
            }

            //array
            if (valueType == ConfigValueTypeEnum.ARRAY) {
                ConfigValueTypeEnum componentValueType = getDataSetValueType(field);
                List<ConfigOptionDescriptor> options = Lists.newArrayList();
                ConfigOptionDescriptor optionDescriptor = new ConfigOptionDescriptor();
                optionDescriptor.setConfigValueType(componentValueType.getValue());
                //数组元素是基础类型没有primary key注解，直接设为false
                optionDescriptor.setPrimaryKey(false);
                options.add(optionDescriptor);
                configOptionDescriptor.setOptionSettings(options);

            }

            //设置当前值
            if (configInst != null) {
                try {
                    field.setAccessible(true);
                    configOptionDescriptor.setConfigValue(field.get(configInst));

                } catch (Exception e) {
                    log.warn("为配置类设置【{}】属性初始值出错，类型【{}】，错误信息：{}", field.getName(), field.getType().getSimpleName(), e.getMessage());
                }

            }

            //对boolean模式的特殊处理，为了解决boolean值为空时前端报错问题
            if (configOptionDescriptor.getConfigValue() == null && configOptionDescriptor.getConfigValueType() == ConfigValueTypeEnum.BOOLEAN.getValue()) {
                configOptionDescriptor.setConfigValue(false);
            }

            //设置默认值
            try {
                field.setAccessible(true);
                //字段值是多个对象
                if (valueType == ConfigValueTypeEnum.DATA_LIST || valueType == ConfigValueTypeEnum.ARRAY) {
                    configOptionDescriptor.setDefaultConfigValue(field.get(field.getDeclaringClass().getDeclaredConstructor().newInstance()));
                }
                //值是单个对象
                else {
                    //设置默认值
                    if (configInst != null) {
                        configOptionDescriptor.setDefaultConfigValue(field.get(configInst));
                    } else {
                        configOptionDescriptor.setDefaultConfigValue(field.get(field.getDeclaringClass().getDeclaredConstructor().newInstance()));
                    }
                }
            } catch (Exception e) {
                log.warn("为配置类设置【{}】属性默认值出错，类型【{}】，错误信息：{}", field.getName(), field.getType().getSimpleName(), e.getMessage());
            }

            //设置DataSource
            configOptionDescriptor.setDataSource(configAttr.dataSource().getValue());
            if (StringUtils.isNotBlank(configAttr.dataSourceApi())) {
                configOptionDescriptor.setDataSourceValueFormat(configAttr.dataSourceValueFormat());
                configOptionDescriptor.setDataSourceText(configAttr.dataSourceText());
                configOptionDescriptor.setDataSourceValue(configAttr.dataSourceValue());
                configOptionDescriptor.setDataSource(DataSourceEnum.API.getValue());
                configOptionDescriptor.setDataSourceApi(configAttr.dataSourceApi());
            }

            //设置 CustomDataSource
            OptionDataSource[] customDataList = configAttr.customDataList();
            if (customDataList != null && customDataList.length > 0) {
                List<CustomDataSource> listCDS = Lists.newArrayList();
                Arrays.stream(customDataList).forEach(c -> {
                    CustomDataSource customDataSource = new CustomDataSource();
                    customDataSource.setText(c.text());
                    customDataSource.setValue(c.value());
                    listCDS.add(customDataSource);
                });
                configOptionDescriptor.setCustomDataList(listCDS);
                configOptionDescriptor.setDataSource(DataSourceEnum.SELECT.getValue());
            } else if (configOptionDescriptor.getConfigValueType() == ConfigValueTypeEnum.ARRAY.getValue() && configAttr.dataSource() == DataSourceEnum.SELECT) {
                log.warn("属性{}的自定义属性ConfigAttribute未设置自定义数据源（CustomDataSource）", field.getName());
            }

            configOptionDescriptor.setCustomPage(configAttr.customPage());
            configOptionDescriptor.setParentKey(configAttr.parentKey());
            configOptionDescriptor.setRemark(configAttr.remark());
            configOptionDescriptor.setParentValue(configAttr.parentValue());
            //配置项信息校验
            validateConfigOptionDescriptors(configOptionDescriptor);
            return configOptionDescriptor;

        } else {
            log.warn("属性【{}】未定义CategoryAttribute", field.getName());
            return null;
        }
    }

    /**
     * 获取集合类型的元素类型
     *
     * @param field
     * @return
     */
    public static ConfigValueTypeEnum getDataSetValueType(Field field) {
        ConfigValueTypeEnum componentValueType = null;
        //集合
        if (Collection.class.isAssignableFrom(field.getType())) {
            ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
            Type[] ctualTypeArguments = parameterizedType.getActualTypeArguments();
            componentValueType = getSimpleValueType(ctualTypeArguments[0].getTypeName());
        }
        //java 数组
        else {
            componentValueType = getSimpleValueType(field.getType().getComponentType().getTypeName());
        }
        return componentValueType;
    }

    private void validateConfigOptionDescriptors(ConfigOptionDescriptor configOptionDescriptor) {
        Integer valueType = configOptionDescriptor.getConfigValueType();
        if (valueType == ConfigValueTypeEnum.DATE.getValue() || valueType == ConfigValueTypeEnum.DATE_RANGE.getValue()) {
            Integer dateFormat = configOptionDescriptor.getDateFormat();
            Assert.isNull(dateFormat, "【" + configOptionDescriptor.getConfigName() + "】配置为日期类型但未设置DateFormat日期格式化属性");
        } else if (valueType == ConfigValueTypeEnum.URL.getValue()) {
            Assert.hasText(configOptionDescriptor.getCustomPage(), "【" + configOptionDescriptor.getConfigName() + "】配置的ConfigAttribute设置成URL类型但未设置CustomPage属性");
        }
    }

    /**
     * 根据属性类型转成成默认配置项值类型
     *
     * @param field
     * @return
     */
    private ConfigValueTypeEnum getValueType(Field field) {

        Class fieldType = field.getType();
        //数组
        if (fieldType.isArray()) {
            return getListValueType(fieldType.getComponentType());
        }
        //集合
        else if (Collection.class.isAssignableFrom(fieldType)) {
            ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
            Type[] ctualTypeArguments = parameterizedType.getActualTypeArguments();
            return getListValueType(ctualTypeArguments[0]);
        } else {
            return getSimpleValueType(fieldType.getTypeName());
        }

    }

    /**
     * 获取java集合类型的配置项类型
     *
     * @param componentType
     * @return
     */
    private ConfigValueTypeEnum getListValueType(Type componentType) {
        ConfigValueTypeEnum genericType = getSimpleValueType(componentType.getTypeName());
        if (genericType == ConfigValueTypeEnum.DATE) {
            return ConfigValueTypeEnum.DATE_RANGE;
        } else if (genericType == ConfigValueTypeEnum.OBJECT) {
            return ConfigValueTypeEnum.DATA_LIST;
        } else {
            return ConfigValueTypeEnum.ARRAY;
        }
    }

    /**
     * 根据java类型名获取配置项值对应简单类型（除集合和数组外的类型）
     *
     * @param javaTypeName
     * @return
     */
    public static ConfigValueTypeEnum getSimpleValueType(String javaTypeName) {
        ConfigValueTypeEnum valueType;
        switch (javaTypeName) {
            //数字类型
            case "int":
            case "class java.lang.Integer":
            case "java.lang.Integer":
            case "Integer":
            case "byte":
            case "class java.lang.Byte":
            case "java.lang.Byte":
            case "Byte":
            case "short":
            case "class java.lang.Short":
            case "java.lang.Short":
            case "Short":
            case "long":
            case "class java.lang.Long":
            case "java.lang.Long":
            case "Long":
            case "float":
            case "class java.lang.Float":
            case "java.lang.Float":
            case "Float":
            case "double":
            case "class java.lang.Double":
            case "java.lang.Double":
            case "Double":
            case "class java.math.BigInteger":
            case "java.math.BigInteger":
            case "BigInteger":
            case "class java.math.BigDecimal":
            case "java.math.BigDecimal":
            case "BigDecimal":
                valueType = ConfigValueTypeEnum.NUMBER;
                break;

            //字符串类型
            case "char":
            case "class java.lang.Character":
            case "java.lang.Character":
            case "Character":
            case "class java.lang.String":
            case "java.lang.String":
            case "String":
                valueType = ConfigValueTypeEnum.STRING;
                break;
            //布尔类型
            case "boolean":
            case "class java.lang.Boolean":
            case "java.lang.Boolean":
            case "Boolean":
                valueType = ConfigValueTypeEnum.BOOLEAN;
                break;
            //日期类型
            case "class java.sql.Timestamp":
            case "java.sql.Timestamp":
            case "Timestamp":
            case "class java.util.Date":
            case "java.util.Date":
            case "Date":
                valueType = ConfigValueTypeEnum.DATE;
                break;
            default:
                valueType = ConfigValueTypeEnum.OBJECT;
        }
        return valueType;

    }

}
